Skip to main content

TCP Server 驱动 v3.2

该驱动内置了一个 tcp server 服务, 驱动启动后可以接收客户端的连接和数据, 同时也可以向客户端发送数据. 如果使用容器部署时, 需要将 tcp 服务端口开放.

注: 每个驱动实例只能添加一个模型

配置说明

以下内容为 tcp-server 驱动的配置项

server:
port: 9404 # TCP 监听的端口, 默认 9404
bufferSize: 4096 # 每个客户端连接的默认缓存冲区大小, 默认 4096

注: tcp.bufferSize 为每个客户端连接的默认的接收缓存大小, 仅当未在模型配置中设置了有效值时生效, 如果模型配置中设置了有效值(大于 0)时则覆盖该配置. 缓存大小需要根据协议内容进行设置, 不能小于单个数据包长度的最大值, 否则无法正常工作. 建议设置为最大数据包长度的 3 倍.

数据处理流程说明

  1. 当接收到客户端发送的数据时, 会读取所有的发送数据并存放到 Buffer 中, 然后调用 数据包拆分脚本
  2. 数据包拆分脚本Buffer 中提取完整的数据包信息或.

脚本说明

脚本语言: JavasScript ECMAScript 5.1

驱动使用时要求提供  数据包拆分脚本, 数据处理脚本指令处理脚本 3 个脚本函数来处理接收和发送数据过程中的协议和数据格式问题.

在脚本的上下文中内置了 Buffer 包, 可用于处理接收或发送二进制数据.

除此之外, 还内置了 lodash, crypto-js, moment, xml-jsformulajs(Excel函数) 包.

注: 所有的脚本函数名必须为 **handler**

客户端对象

数据包拆分脚本, 数据处理脚本, 指令处理脚本连接处理脚本 函数的参数中提供了 client 对象, 该对象为当前 TCP 连接客户端对象, 可以通过该对象实现向客户端发送数据的功能. 例如: 向客户端发送 ack 信息等.

该对象提供了以下函数:

register

用于注册设备. 接收到客户端发送的设备注册信息时, 在脚本函数中通过调用 client 对象中的 register 函数注册该设备, 驱动会将该设备与当前连接绑定. 当需要向设备发送指令时, 会通过该绑定关系向对应的连接发送数据. 如果协议未定义设备注册, 会向上次接收到该设备数据的连接发送. 详细信息见 设备与连接绑定关系说明

参数说明
参数名参数类型参数说明示例值
array[string]字符串列表设备标识列表["d01", "d02"]
返回值

stringundefined 如果参数不正确、部分设备标识错误或对应的设备不存在时返回 string, 内容为错误说明. 如果所有设备注册成功, 返回 undefined

示例
function handler(client, buffer) {
client.register(["device01", "device02"]);
}

unregister

用于注销设备. 注销设备与连接的绑定关系, 参数及返回值说明同 register

示例
function handler(client, buffer) {
client.unregister(["device01", "device02"]);
}

send

用于向客户端发送数据. 例如: 在接收到数据时向客户端发送 ack 信息.

参数说明
参数名参数类型参数说明示例值
dataBuffer发送的内容Buffer.from("hello") 代表要发送 "hello"
返回值

stringundefined 如果参数不正确(字节数组为空或空数组)或发送失败则返回 string 内容为错误说明, 如果发送成功则返回 undefined.

示例
function handler(client, buffer) {
// 向客户端发送 hello world
client.send(Buffer.from("hello world"));
}

getRemoteAddr

用于获取客户端地址信息.

参数说明

返回值

string. 格式: IP:Port. 例如: 192.0.2.1:25

示例
var remoteAddr = client.getRemoteAddr();

getRemoteIp

用于获取客户端 IP 地址信息.

参数说明

返回值

string. 例如: 192.0.2.1

示例
var clientIp = client.getRemoteIp();

reportCommand

上报指令执行结果. 有些场景指令的执行结果反馈是异步的, 在指令发送后, 过一段时间才会收到响应报文, 此时可以在接收到响应报文时通过 client.reportCommand(...) 方法上报指令执行结果.

参数说明
参数名参数类型参数说明示例值
serialNoString平台指令序号"f160a24b-1780-89e7-cd48-b4c0073bd0fe"
deviceIdString设备标识"ST10001"
stateString状态标识自定义状态标识, 例如: Success, Failed 等
resultString结果数据有些指令可能有响应数据, 可以将结果数据保存在 result 字段中. 例如: 读取设备配置指令, 会返回设备配置信息

注: serialNo 为平台在发送指令时生成, 会传入到 指令处理脚本 中, 当指令执行结果为异步反馈时, 可以将 serialNo 保存到 设备上下文 中, 以便在后续 reportCommand 使用

返回值

stringundefined. 如果返回 undefined 则表示发送成功, 否则返回数据为错误原因.

示例

// 模拟指令处理脚本, 假设指令执行结果为异步反馈
function handler(client, serialNo, deviceId, commmand) {
// 获取设备上下文
const deviceContext = client.getDeviceContext(deviceId);

// 将平台指令序号保存到设备上下文中, 以便后面使用
deviceContext.put(command.name, {"serialNo": serialNo});

// 其它
}

// 数据处理脚本, 模拟收到指令执行结果
function handler(client, buffer) {
// 假设读取到的数据为指令执行结果

// 从数据中读取出设备标识
const deviceId = buffer.slice(4, 20).toString();
// 从数据中读取出指令标识
const commandName = buffer.slice(20, 30).toString();
// 批令执行成功标识
const state = buffer[30];
// 执行结果信息
const result = buffer.slice(31, 50).toString();

// 获取设备上下文
const deviceContext = client.getDeviceContext(deviceId);
// 从设备上下文中获取并删除指令信息
const command = deviceContext.getAndRemove(commandName);

// 发送指令执行结果
const sendResult = client.reportCommand(command.serialNo, deviceId, state == 0 ? 'SUCCESS' : 'FAILED', result);
if (!sendResult) {
console.log("批令发送失败:", sendResult);
}
}

getMediaFile

请求媒体库文件

参数说明
参数名参数类型参数说明示例值
pathString文件路径文件在媒体库的路径. 例如: /background/bg1.png 为目录 background 中的 bg1.png 文件
返回值
参数名参数类型参数说明示例值
successbool是否成功请求是否成功. true: 成功, false: 失败
messagestring信息请求失败时为失败原因
dataobject文件信息请求成功时该字段存在
namestring文件名文件名. 例如: bg1.png
sizenumber文件大小单位: 字节
dataBuffer文件内容Buffer中包含文件内的全部数据
示例
function handler(client) {
const response = client.getMediaFile("/background/bg1.png");
if (!response.success) {
console.log("请求媒体库文件失败:", response.message);
return;
}

// 文件信息
const fileInfo = response.data;
// 文件名
const filename = fileInfo.name;
// 文件大小
const fileSize = fileInfo.size;
// 文件内容
const fileData = fileInfo.data;
}

getMediaFileByURL

请求媒体库文件

参数说明
参数名参数类型参数说明示例值
urlString文件 url文件在媒体库请求 url, 该信息一般由媒体库组件或附件组件获得. 例如: /core/fileServer/mediaLibrary/default/test/hello.txt

注: url 中 default 为项目ID, test 为目录, hello.txt 为文件名.

返回值
参数名参数类型参数说明示例值
successbool是否成功请求是否成功. true: 成功, false: 失败
messagestring信息请求失败时为失败原因
dataobject文件信息请求成功时该字段存在
namestring文件名文件名. 例如: bg1.png
sizenumber文件大小单位: 字节
dataBuffer文件内容Buffer中包含文件内的全部数据
示例
function handler(client) {
const response = client.getMediaFileByURL("/core/fileServer/mediaLibrary/default/test/hello.txt");
if (!response.success) {
console.log("请求媒体库文件失败:", response.message);
return;
}

// 文件信息
const fileInfo = response.data;
// 文件名
const filename = fileInfo.name;
// 文件大小
const fileSize = fileInfo.size;
// 文件内容
const fileData = fileInfo.data;
}

uploadMediaFile

上传文件到媒体库

参数说明
参数名参数类型参数说明示例值
filenameString文件名例如: hello.txt
catalogString目录文件上传到的目录, 支持多级目录. 例如: image/background
actionString存在同名文件时的动作cover: 覆盖已有文件, rename: 新上传文件名后面自动加1
dataBuffer文件内容文件的内容
返回值
参数名参数类型参数说明示例值
successbool是否成功请求是否成功. true: 成功, false: 失败
messagestring信息请求失败时为失败原因
示例
function handler(client) {
// 上传 hello.txt 到 data/note 目录, 文件内容为 "hello world"
const response = client.uploadMediaFile("hello.txt", "data/note", "cover", Buffer.from("hello world"));
if (!response.success) {
console.log("上传文件失败:", response.message);
return;
}
}

deleteMediaFile

删除媒体库文件

参数说明
参数名参数类型参数说明示例值
pathString文件路径文件在媒体库的路径. 例如: /background/bg1.png 为目录 background 中的 bg1.png 文件
返回值
参数名参数类型参数说明示例值
successbool是否成功请求是否成功. true: 成功, false: 失败
messagestring信息请求失败时为失败原因
示例
function handler(client) {
// 删除目录 background 中的 bg1.png 文件
const response = client.deleteMediaFile("/background/bg1.png");
if (!response.success) {
console.log("删除媒体库文件失败:", response.message);
return;
}
}

saveWorkTableRow

向工作表写入一条数据

参数说明
参数名参数类型参数说明示例值
tableIdString工作表标识例如: student
rowobject写入数据该字段为 JSON 对象, 内容根据工作表定义填写. 例如: {"name": "小明", "age": 18}
返回值
参数名参数类型参数说明示例值
successbool是否成功请求是否成功. true: 成功, false: 失败
messagestring信息请求失败时为失败原因
datastring记录ID请求成功时, 为新增记录的的ID
示例
function handler(client) {
// 删除目录 background 中的 bg1.png 文件
const response = client.saveWorkTableRow("student", {"name": "小明", "age": 18});
if (!response.success) {
console.log("写入数据失败:", response.message);
return;
}

// 新增记录ID
const rowId = response.data;
}

updateWorkTableRow

更新工作表中的数据

参数说明
参数名参数类型参数说明示例值
tableIdString工作表标识例如: student
queryobject过滤条件该字段为 JSON 对象, 过滤出要更新哪些记录. 例如: {"name": "小明"} 更新 name 为 "小明" 的所有记录
rowobject写入数据该字段为 JSON 对象, 内容根据工作表定义填写. 例如: {"age": 19}  更新 age 字段的值为 19
返回值
参数名参数类型参数说明示例值
successbool是否成功请求是否成功. true: 成功, false: 失败
messagestring信息请求失败时为失败原因
示例
function handler(client) {
// 更新 name 字段值为 "小明" 所有记录的 age 字段的值为 19
const response = client.updateWorkTableRow("student", {"name": "小明"}, {"age": 19});
if (!response.success) {
console.log("更新数据失败:", response.message);
return;
}
}

updateNodeById

根据资产标识更新资产数据

参数说明
参数名参数类型参数说明示例值
nodeIdString资产标识例如: ST10001
dataobject更新内容该字段为 JSON 对象, 要更新的内容. 例如: {"name": "小明", "age": 18}
返回值
参数名参数类型参数说明示例值
successbool是否成功请求是否成功. true: 成功, false: 失败
messagestring信息请求失败时为失败原因
示例
function handler(client) {
// 更新 name 字段值为 "小明" 所有记录的 age 字段的值为 19
const response = client.updateNodeById("ST10001", {"name": "小明", "age": 18});
if (!response.success) {
console.log("更新资产失败:", response.message);
return;
}
}

getContext

用于获取上下文对象, 可以在上下文中存储数据. 详细信息

注: 第一次调用的时候才会创建上下文

参数说明
参数名参数类型参数说明示例值
contextIdString上下文标识"myContext"
返回值

Object. 上下文对象

示例
function handler(client) {
// 获取一个标识为 myContext 的上下文
var myContext = client.getContext("myContext");

// 使用客户端 IP 地址作为标识
var context = client.getContext(client.getRemoteIp());
}

getDeviceContext

用于获取设备上下文对象, 使用设备标识作为上下文标识, 可以在上下文中存储数据. 详细信息

getContext 不同的是, 当设备被删除后, 重启驱动时会自动清理被删除设备的上下文对象.

注: 第一次调用的时候才会创建上下文

参数说明
参数名参数类型参数说明示例值
deviceIdString设备标识"ST10001"
返回值

Object. 上下文对象

示例
function handler(client) {
// 获取设备 ST10001 的上下文
var context = client.getDeviceContext("ST10001");
}

removeContext

删除上下文对象

参数说明
参数名参数类型参数说明示例值
contextIdString上下文标识"myContext"
返回值

示例
function handler(client) {
// 删除标识为 myContext 的上下文
client.removeContext("myContext");

// 删除以客户端 IP 地址作为标识的上下文
client.removeContext(client.getRemoteIp());
}

removeDeviceContext

删除设备上下文对象

参数说明
参数名参数类型参数说明示例值
deviceIdString上下文标识"ST10001"
返回值

示例
function handler(client) {
// 删除设备 ST10001 的上下文
client.removeDeviceContext("ST10001");
}

getContextIds

获取全部上下文标识(不包含设备上下文)

参数说明

返回值

String[]

示例
function handler(client) {
// 创建两个上下文
const context1 = client.getContext("context1");
const context2 = client.getContext("context2");

// 获取全部上下文标识 ["context1", "context2"]
const contextIds = client.getContextIds();
}

getDeviceContextIds

获取全部设备上下文标识(只包含设备上下文)

参数说明

返回值

String[]

示例
function handler(client) {
// 创建两个设备上下文
const context1 = client.getDeviceContext("ST10001");
const context2 = client.getDeviceContext("ST10002");

// 获取全部设备上下文标识 ["ST10001", "ST10002"]
const contextIds = client.getDeviceContextIds();
}

context

上下文对象, 用来存储数据, 上下文中的数据可以在不同的脚本中共享. 例如: 可以在 指令处理脚本中 写入数据, 然后从 数据处理脚本 中读取数据. 不同上下文彼此独立, 互不影响.

注: 可以根据需求创建多个上下文对象, 但是上下文对象以及上下文中的数据需要及时清理, 否则会造成 **OOM** 问题, 导致驱动程序崩溃.

put

用于向上下文中存储数据.

注: 存入的数据需要自行清理, 否则可能导致 OOM 等问题.

参数说明
参数名参数类型参数说明
keystring数据项的 key
valueany数据项的值
返回值

示例
function handler(client, request) {
// 获取或创建标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 向上下文中存储一个数值
context.put("number", 3.141);
// 向上下文中存储一个对象
context.put("object", {name: "张三", age: 18});

// 获取或创建设备 ST10001 的上下文
const deviceContext = client.getDeviceContext("ST10001");
// 向上下文中存储一个字符串
deviceContext.put("string", "this is a string");
// 向上下文中存储一个数值
deviceContext.put("number", 3.141);
// 向上下文中存储一个对象
deviceContext.put("object", {name: "张三", age: 18});
}

containsKey

判断上下文中是否存在指定的 key

参数说明
参数名参数类型参数说明
keystring数据项的 key
返回值

bool. true 表示 key 存在, false 表示 key 不存在

示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");

// 返回 true
context.containsKey("string");
// 返回 false
context.containsKey("string1");
}

get

从上下文中获取指定的 key 对应的数据. 如果 key 不存在则返回 undefined

参数说明
参数名参数类型参数说明
keystring数据项的 key
返回值

anyundefined. 返回 put 时写入的数据.

示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");

// 返回 "this is a string"
context.get("string");

// 返回 undefined
context.get("string1");
}

getAndRemove

从上下文中获取指定的 key 对应的数据并且在返回后 删除key. 如果 key 不存在则返回 undefined.

注: 该函数返回后, 再使用 getgetAndRemove 均返回 undefined.

参数说明
参数名参数类型参数说明
keystring数据项的 key
返回值

anyundefined. 返回 put 时写入的数据.

示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");

// 向上下文中存储一个字符串
context.put("string", "this is a string");

// 返回 "this is a string"
context.getAndRemove("string");

// 返回 undefined
context.get("string");

// 返回 undefined
context.getAndRemove("string");
}

remove

从上下文中删除指定的 key, 如果 key 不存在则不执行任何操作.

参数说明
参数名参数类型参数说明
keystring数据项的 key
返回值

示例
function handler(client) {
// 获取标识为 myContext 的上下文
const context = client.getContext("myContext");
// 向上下文中存储一个字符串
context.put("string", "this is a string");
// 数据被删除
context.remove("string");
// 不执行任何操作
context.remove("string");
}

内置函数

crc 校验

驱动中内置了 crc 对象, 可以在脚本中直接使用 crc 对象中的校验函数.

使用 checksum16 实现 crc16 校验
function handler() {
// 示例数据
const data = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
// 多项式
const poly = 0x1234;

// 返回 uint16 的校验码
const checksum = crc.checksum16(data, poly);
// 使用内置函数, 将校验码转换为字节数组(大端字节序)
const checksumBytes = bigEndian.encodeUint16(checksum);
}
使用 checksum32 实现 crc32 校验
function handler() {
// 示例数据
const data = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
// 多项式
const poly = 0xedb88320;

// 返回 uint32 的校验码
const checksum = crc.checksum32(data, poly);
// 使用内置函数, 将校验码转换为字节数组(大端字节序)
const checksumBytes = bigEndian.encodeUint32(checksum);
}
使用 checksum64 实现 crc64 校验
function handler() {
// 示例数据
const data = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
// 多项式
const poly = 0xD800000000000000;

// 返回 uint64 的校验码
const checksum = crc.checksum64(data, poly);
// 使用内置函数, 将校验码转换为字节数组(大端字节序)
const checksumBytes = bigEndian.encodeUint64(checksum);
}
使用 checksumModbus 实现 modbus 协议 crc 校验

modbus 校验为 crc16, 所以校验码类型为 uint16. 另外, checksumModbus 不需要 poly 多项式参数.

function handler() {
// 示例数据
const data = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]);

// 返回 uint16 的校验码
const checksum = crc.checksumModbus(data);
// 使用内置函数, 将校验码转换为字节数组(大端字节序)
const checksumBytes = bigEndian.encodeUint16(checksum);
}

数据包拆分脚本

该脚本用于处理接收数据过程中的半包和粘包问题, 该函数需要根据协议和数据格式判断接收到的字节数组中是否包含完整的数据包, 如果包含了完整数据包则返回完据包的起始位置和长度. 或者当数据包有错误时丢掉该数据包或有问题部分的数据.

如果字节数组中不包含完整的数据包, 直接返回 undefined 表示需要从客户端接收更多的数据.

函数定义如下:

/**
* 数据包拆分脚本, 从字节流中提取出完整的数据包, 或丢弃部分数据.
*
* @param {Object} client 客户端对象
* @param {Buffer} data 已接收但未处理的数据
* @return {Object}
*/
function handler(client, buffer) {
// 拆包逻辑
return {"package": {"start": 0, "length": 128, "next": 129}, "drop": 0};
}
参数说明
参数名参数类型参数说明
clientobject客户端连接对象
bufferBuffer从客户端接收到的内容
返回值说明
  • 当字节数组中包含完整数据包时, 返回包含 package 字段的对象.
  • 如果字节数组中存在错误, 则返回包含 drop 字段的对象, 丢弃掉错误的数据.
  • 如果字节数组中即不包含完整的数据包也不存在错误, 直接返回 undefined, 表示需要从客户端接收更多的数据. | 参数名 | 参数类型 | 参数说明 | 示例值 | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------- | ---------------------------------------- | | package | object | 数据包信息, 如果要丢弃数据该字段不需要返回 | {"start": 0, "length": 128, "next": 129} | | start | 数值 | 数据包在 data | | 中的起始位置 | 4 表示第 5 个字节为数据包的起始位置 | | length | 数值 | 数据包的长度, 表示从 start | | 后多少个字节为一个完整的数据包 | start=4, length=128 表示从第 5 个字节开始(包括第 5 个字节)连续 128 个字节为一个完整的数据包 | | next | 数值 | 下一个数据包的起始位置 | 例如: 在使用 \\r\\n | | 作为数据包间的固定分隔符时, 下一个数据包的起始位置应该为 start + length + 2 | | drop | 数值 | 要丢弃的字节数量, 即丢弃字节数组中 data[0:drop] | | 内容 | 128 表示丢弃前 128 个字节. 一般用于数据包错误时丢弃掉错误的数据包场景 |
正常接收数据

当数据包拆分脚本返回以下内容时, 表示已接收到的数据中包含了完整的数据包. 该数据包在所有数据中的起始位置为 0, 长度为 128, 下一个数据包的起始位置为 129

{
"package": {
"start": 0,
"length": 128,
"next": 129
}
}

例如, 使用 \r\n 作为数据包间的分隔符, 以解析该数据时 {"a":1,"b":2}\r\n{"a":3,"b":4}\r\n, 拆分结果为 {"package":{"start":0,"length":13,"next":15}}. 此时, 数据处理脚本 中接收到的数据为 {"a":1,"b":2}, 并不会包含 \r\n, 而下一次数据拆分脚本接收到的数据为 {"a":3,"b":4}\r\n.

包含错误或不完整数据包时

返回以下内容时, 表示接收到的数据不完整, 需要丢弃 0 - 12 部分内容.

{
"drop": 12
}

例如, 接收到的内容为 :1,"b":2}\r\n{"a":3,"b":4}\r\n 时, :1,"b":2}\r\n 为不完整的数据包需要丢弃, 则需要返回 {"drop":11}. 下一次数据拆分脚本接收到的数据为 {"a":3,"b":4}\r\n

不包含完整的数据包和错误包时

例如, 接收到的数据为 {"a":1, 未找到分隔符 \r\n, 此时返回 undefined 表示需要接收更多的数据. 当客户端发送 "b":2}\r\n{"a":3,"b":4}\r\n 数据时, 会再次调用 数据包拆分脚本, 此时 Buffer 中的数据为 {"a":1,"b":2}\r\n{"a":3,"b":4}\r\n, 此时可以提取出完整的数据包.

注: 如果返回结果中同时包含了 **drop****package** 字段并且 **drop** 的值大于 0 时, 只做丢弃数据处理.

示例

// 校验数据包
function check(data) {
return true;
}

// 使用固定分隔符进行拆包实例逻辑
function handler(client, buffer) {
// 查找分隔符 \r\n
const index = buffer.indexOf("\r\n", 0);
// 如果未找到分隔符, 表示当前字节数组中不包含完整的数据包, 直接返回 undefined
if (index < 0) {
return undefined;
}

// 从 buffer 复制出完整数据包
const pkg = Buffer.alloc(index);
buffer.copy(pkg, 0, 0, index);
// 数据包校验, 如果数据错误可直接返回 drop
if (!check(pkg)) {
return {"drop": index + 2};
}

return {"package": {"start": 0, "length": index, "next": index + 2}};
}
驱动内置数据包拆包函数
  • 固定长度头 该函数会取前 N 个字节作为长度信息, 然后根据长度信息读取主体内容. +--------+-----------+ | Length | Data | +--------+-----------+

在数据包拆分脚本中直接使用内置的拆分函数, 如下所示:

// 创建长度头为 4 个字节, 长度头为小端字节序
// 第 1 个参数为长度占用的字节数量
// 第 2 个参数为长度头的字节序, 取值可以为 little 或 big
const handler = createFixedLengthSplitFn(4, 'little');
  • 固定分隔符 该函数会使用固定分隔符拆分数据包. 使用方式如下所示:
// 创建使用  \r\n 作为分隔符
const handler = createDelimiterSplitFn('\r\n');
  • 固定开始和结束符 该函数会读取指定开始符和结束符之间的数据做为一个完整的数据包. 使用方式如下所示:
// 创建以 '@' 开头并且以 '#' 结尾的数据包拆分函数
const handler = createStartEndDelimiterSplitFn('@', '#');

数据处理脚本

该脚本用于处理 数据包拆分脚本 函数解析得到的完整数据包, 根据协议和数据格式将数据包解析为平台定义的数据格式.

函数定义如下:

/**
* 数据处理脚本, 解析从客户端接收到的数据并转换为平台规定的数据格式
*
* @param {Object} client 客户端对象
* @param {Buffer} 由 '数据包拆分脚本' 拆分得到的完整数据包
* @return {Array} 解析出的采集数据信息
*/
function handler(client, buffer) {
// 数据包处理逻辑

// 如果返回空数组或 undefined, 则表示未解析出任何有效数据

// 返回结果必须为数组, 数组中每个元素为一个设备的实时数据信息
return [
{
"id": "d01", // 设备标识
"time": 1665999863637, // 数据采集时间(ms), unix 时间戳
"values": {"key1": "str", "key2": 123} // 数据点, key 为数据点的标识, value: 为数据点的值
}
];
}
参数说明
参数名参数类型参数说明
clientobject客户端连接对象
bufferBuffersplitHandler
函数得到的数据包
返回值
参数名参数类型参数说明示例值
array[object]对象数组返回值[{"id":"d01","time":1665999863637,"values":{"temperature":17.5,"humidity":35.7}}]
id字符串资产编号或设备标识d01
time数值时间戳(ms)1664256913000
fields对象数据点信息{"temperature":17.5,"humidity":35.7}
key字符串数据点标识"temperature"
valueany数据点的值17.5

注: 返回值必须为 **Object[]****undefined** 其中之一. 返回空数组表示未从接收到的数据中解析出有效数据, **undefined**表示无返回结果, 驱动程序无须处理返回结果. 例如: 接收到的数据为心跳数据, 不包含采集数据.

示例
function handler(client, buffer) {
// 以 json 格式为例, 例如: {"id":"d01","time":"2022-10-17 17:57:32","values":[{"name":"temperature","data":17.5},{"name":"humidity","data":35.7}]}
const jsonData = JSON.parse(buffer.toString());
const time = moment(jsonData.time, "YYYY-MM-DD HH:mm:ss");
const values = {};
for (let i = 0; i < jsonData.values.length; i++) {
const value = jsonData.values[i];
values[value.name] = value.data;
}

return [
{id: jsonData.id, values: values, time: time.valueOf()}
]
}

指令处理脚本

该脚本用于将发送的指令内容转换为字节数组, 当向设备发送指令时, 驱动会将要发送的内容先经过 命令处理脚本 函数处理, 返回结果作为实际发送的内容.

函数定义如下:

/**
* 指令处理脚本, 当发送指令时将指令信息转换为字节数组
*
* @param {object} client 客户端对象
* @param {string} serialNo 平台指令下发序号
* @param {string} deviceId 设备标识
* @param {object} command 指令信息, 详细格式说明见驱动配置文档
* @return {Buffer} 最终发送数据
*/
function handler(client, serialNo, deviceId, command) {
// 数据转换处理, 将待发送内容转换为 Buffer

// 返回结果必须为字节数组
return Buffer.from("hello"); // 表示发送 "hello"
}
参数说明
参数名参数类型参数说明
clientobject客户端对象
serialNostring平台指令下发序号
deviceIdstring自定义设备标识
commandobject指令信息, 格式如下

指令格式如下:

test 指令为例

{
"name": "test",
"showName": "测试",
"ops": [
{
"value": "123"
}
],
"params": {
"test": {
"attr1": "value1",
"attr2": 123
}
}
}
字段名参数类型参数说明
name字符串指令名称
showName字符串指令显示名称
ops.value字符串指令中配置的发送内容
params对象数据写入配置
defaultValue对象数据写入配置中各字段的默认值

注: ops.value 通常为实际发送的内容, 该字段为必填值. opts 为数组, 目前长度固定为 1.

当要发送的数据内容比较复杂时, 可以先转发送内容转换为 base64 格式的字符串, 然后在在指令脚本中再进行 base64 解码后再发送.

返回值说明

必须值必须为 Buffer 对象.

注: 可以使用 Buffer.from("this is a string"") 等方法创建 Buffer 对象. 关于 Buffer 的使用说明可以点击查看

示例
// 指令发送内容为 base64 格式, 在发送时先进行 base64 解码再发送
function handler(client, serialNo, deviceId, command) {
// 从指令信息中取出要发送的内容, 格式为 base64
const value = data.ops[0].value;
// 将要发送的内容转换为 buffer, 由于指令中的发送内容为 base64, 所在指定编码
return Buffer.from(value, 'hex');
}

连接处理脚本

当客户端连接到驱动或断开时, 会调用 连接处理脚本.

函数定义如下:

/**
* 连接处理脚本, 当与服务端连接建立或断开时执行的操作
*
* @param {Object} client 客户端对象
* @param {boolean} state 连接状态, true: 已连接, false: 已断开
*/
function handler(client, state) {
if (state) {
// 当客户端连接到驱动时执行操作
} else {
// 当连接断开时执行操作
}
}
参数说明
参数名参数类型参数说明
clientobject客户端对象
statebool连接状态. true: 连接已建立, false: 连接已断开
返回值说明

无返回值

示例
function handler(client, state) {
// 当客户端连接到驱动时, 向客户端发送指定数据
if (state) {
client.send(Buffer.from("start report"));
} else {
// 连接断开时, 执行一些操作. 例如: 清理上下文对象等
}
}

设备与连接绑定关系说明

设备与连接关系绑定, 主要用于向设备发送指令时, 驱动会根据该绑定关系确认通过哪个连接发送指令数据. 绑定关系的创建有以下两种:

  • 当协议中定义了设备注册功能时, 由 数据处理脚本 函数在接收到设备注册数据包时, 通过调用 client 中的 register 向驱动注册设备实现设备与连接折绑定.
  • 当协议中未定义设备注册功能时, 驱动会自动记录每个设备是由哪个连接上报实时数据的(设备每次上传实时数据时都会自动更新), 当驱动下发指令时, 会自动根据该信息向对应的连接发送指令数据.

注: 如果通过 **register** 函数注册了设备时, 会忽略根据实时数据建立的绑定关系. 如果协议中未定义设备注册功能并且设备未上报过实时数据时, 无法通过实时数据建立绑定关系, 此时无法向该设备发送数据